package org.geotools.tutorial;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.JMapPane;
import org.geotools.swing.action.SafeAction;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.event.MapMouseEvent;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.ProgressListener;
import com.vividsolutions.jts.geom.Geometry;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.styling.StyleFactory;
import org.geotools.swing.event.MapMouseAdapter;
import org.geotools.swing.event.MapMouseListener;
import org.geotools.swing.tool.CursorTool;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;

import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.tutorial.MyStyle.GeomType;
import org.opengis.referencing.operation.MathTransform;

import org.geotools.geosearch.Guide;
/**
 * This is a visual example of changing the coordinate reference system of a feature layer.
 */
public class CRSLab {

    private File sourceFile;
    private SimpleFeatureSource featureSource;
    private MapContext map;
    private JMapFrame mapFrame;

    /*
     * Factories that we will use to create style and filter objects
     */
    private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);
    private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);

    /*
     * Convenient constants for the type of feature geometry in the shapefile
     */
    private String geometryAttributeName;
    private GeomType geometryType;
    CoordinateReferenceSystem oldCRS;
    CoordinateReferenceSystem newCRS;
    SimpleFeatureCollection path = null;

    public SimpleFeatureCollection getPath() {
        return path;
    }

    public void setPath(SimpleFeatureCollection path) {
        if (path == null) {
            return;
        }
        this.path = path;
        mapFrame.getMapContext().addLayer(path, ms.createPathStyle());
    }

    private void displayShapefile() throws Exception {
        //sourceFile = JFileDataStoreChooser.showOpenFile("shp", null);

        sourceFile = new File("improvedmap/improved0.04.shp");
        if (sourceFile == null) {
            return;
        }
        FileDataStore store = FileDataStoreFinder.getDataStore(sourceFile);
        featureSource = store.getFeatureSource();

        // Create a map context and add our shapefile to it
        map = new MapContext();
        MyStyle ms = new MyStyle(GeomType.LINE);
        map.addLayer(featureSource, ms.createDefaultStyle());

        //map.addLayer(featureSource, ms.createDefaultStyle());

        oldCRS = map.getCoordinateReferenceSystem();
        newCRS = CRS.decode("EPSG:32637");
        map.setCoordinateReferenceSystem(newCRS);

        // Create a JMapFrame with custom toolbar buttons

        mapFrame = new JMapFrame();

        mapFrame.setMapContext(new DefaultMapContext(newCRS));
        //mapFrame.getMapPane().getMapContext().addLayer(featureSource, ms.createDefaultStyle());
        mapFrame.enableToolBar(true);
        mapFrame.enableStatusBar(true);
        mapFrame.enableLayerTable(true);


        JToolBar toolbar = mapFrame.getToolBar();
        toolbar.addSeparator();
        toolbar.add(new JButton(new ValidateGeometryAction()));
        toolbar.add(new JButton(new ExportShapefileAction()));
        toolbar.add(new JButton(new FindMap()));
        JButton btn = new JButton("Select");
        toolbar.addSeparator();
        toolbar.add(btn);
        btn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                mapFrame.getMapPane().setCursorTool(
                        new CursorTool() {

                            @Override
                            public void onMouseClicked(MapMouseEvent ev) {
                                try {
                                    selectFeatures(ev);
                                } catch (FactoryException ex) {
                                    Logger.getLogger(CRSLab.class.getName()).log(Level.SEVERE, null, ex);
                                } catch (TransformException ex) {
                                    Logger.getLogger(CRSLab.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                        });
            }
        });

        //добавляем кнопку Guide. Только она учитывает направление
        Guide guide = new Guide(featureSource, oldCRS, newCRS, 0);
        toolbar.add(new GuideButton( mapFrame.getMapPane(), featureSource, guide, sf, "simple guide",oldCRS,newCRS));

        //Добавляем поиск безсветофорного пути
        Guide guide2 = new Guide(featureSource, oldCRS, newCRS, 1);
        toolbar.add(new GuideButton( mapFrame.getMapPane(), featureSource, guide2, sf, "with no signal",oldCRS,newCRS));

        //s
        Guide guide3 = new Guide(featureSource, oldCRS, newCRS, 2);
        toolbar.add(new GuideButton( mapFrame.getMapPane(), featureSource, guide3, sf, "avr",oldCRS,newCRS));

        //Добавляем кнопку "Карта Москвы"
        toolbar.add(new JButton(new SafeAction("Карта Москвы") {

            @Override
            public void action(ActionEvent e) throws Throwable {
                File sourceFileMoscow = new File("/home/oleg/Документы/moscow/moscow-roads-l.shp");
                FileDataStore storemsk = FileDataStoreFinder.getDataStore(sourceFileMoscow);
                MyStyle ms2 = new MyStyle(GeomType.LINE);
                mapFrame.getMapContext().addLayer(storemsk.getFeatureSource(), ms2.createDefaultStyle());
            }
        }));

        //Добавляем кнопку "Карта Калужской области"
        toolbar.add(new JButton(new SafeAction("Карта Калужской области") {

            @Override
            public void action(ActionEvent e) throws Throwable {
                File sourceFileMoscow = new File("/home/oleg/Документы/kaluzh/kaluzh-roads-l.shp");
                FileDataStore storemsk = FileDataStoreFinder.getDataStore(sourceFileMoscow);
                MyStyle ms2 = new MyStyle(GeomType.LINE);
                mapFrame.getMapContext().addLayer(storemsk.getFeatureSource(), ms2.createDefaultStyle());
            }
        }));

        //Добавляем кнопку "Карта Калужской области"
        toolbar.add(new JButton(new SafeAction("Добавить слой") {

            @Override
            public void action(ActionEvent e) throws Throwable {
                File sourceFile = JFileDataStoreChooser.showOpenFile("shp", null);
                FileDataStore storemsk = FileDataStoreFinder.getDataStore(sourceFile);
                MyStyle ms2 = new MyStyle(GeomType.LINE);
                mapFrame.getMapContext().addLayer(storemsk.getFeatureSource(), null);
            }
        }));

        //
        Guide guide4 = new Guide(featureSource, oldCRS, newCRS, 1);
        DirectionImprovement improvement=new DirectionImprovement(featureSource);
        DirectionButton directionButton=new DirectionButton(mapFrame, featureSource, mapFrame.getMapPane(), guide4, sf, "direction",improvement,oldCRS,newCRS);

        toolbar.add(directionButton);
        
        //
        toolbar.add(new DirectionButtonSave(improvement));

        mapFrame.getMapPane().setDisplayArea(map.getLayerBounds());
        // Display the map frame. When it is closed the application will exit
        mapFrame.setSize(800, 600);
        mapFrame.setVisible(true);

        JMapPane mapPane = mapFrame.getMapPane();
        MapMouseListener mapMouseListener = new MapMouseAdapter() {

            public void onMouseClicked(MapMouseEvent ev) {
                super.onMouseClicked(ev);
                JOptionPane.showMessageDialog(null, "" + ev.getMapPosition().toString(), "Geometry results", JOptionPane.INFORMATION_MESSAGE);
            }
        };
        //mapPane.addMouseListener(mapMouseListener);

        setGeometry();
        /*
        SimpleFeatureType schema = (SimpleFeatureType) featureSource.getSchema();
        JSimpleStyleDialog.showDialog(null, schema);
         *
         */

        // Поиск встроенным алгоритмом Дейкстры
        MyGraph mg = new MyGraph(featureSource);
        mg.function1(null, null);

        mapFrame.getMapContext().addLayer(featureSource, null);
    }
    org.geotools.tutorial.MyStyle ms;

    private void createStyle() {
        ms = new MyStyle(geometryType);
        Set<FeatureId> IDs = new HashSet<FeatureId>();
        ms.displaySelectedFeatures(IDs, mapFrame);
    }

    class FindMap extends SafeAction {

        public FindMap() {
            super("Карта целиком");
            putValue("Уместить содержимое карты в окне", enabled);
        }

        @Override
        public void action(ActionEvent ae) throws Throwable {
            ReferencedEnvelope envelope = map.getLayerBounds();
            mapFrame.getMapPane().setDisplayArea(envelope);
        }
    }

    class MyAction extends SafeAction {

        public MyAction() {
            super("my action");
            putValue(Action.SHORT_DESCRIPTION, "just my action");
        }

        public void action(ActionEvent ae) throws Throwable {
            int numInvalid = featuredGeometry(null);
            String msg;
            msg = "count: " + numInvalid;

            JOptionPane.showMessageDialog(null, msg, "Geometry results",
                    JOptionPane.INFORMATION_MESSAGE);
        }
    }

    private int featuredGeometry(ProgressListener progress) throws Exception {
        final SimpleFeatureCollection featureCollection = featureSource.getFeatures();

        // Rather than use an iterator, create a FeatureVisitor to check each fature
        class ValidationVisitor implements FeatureVisitor {

            public int counter = 0;

            public void visit(Feature f) {
                SimpleFeature feature = (SimpleFeature) f;
                Geometry geom = (Geometry) feature.getDefaultGeometry();
                if (geom != null && geom.isValid()) {
                    counter++;
                    System.out.println("geom is valid");
                }
            }
        }

        ValidationVisitor visitor = new ValidationVisitor();

        // Pass visitor and the progress bar to feature collection
        featureCollection.accepts(visitor, progress);
        return visitor.counter;
    }

    public static void main(String[] args) throws Exception {
        CRSLab lab = new CRSLab();
        lab.displayShapefile();
    }

    class ValidateGeometryAction extends SafeAction {

        ValidateGeometryAction() {
            super("Validate geometry");
            putValue(Action.SHORT_DESCRIPTION, "Check each geometry");
        }

        public void action(ActionEvent e) throws Throwable {
            int numInvalid = validateFeatureGeometry(null);
            String msg;
            if (numInvalid == 0) {
                msg = "All feature geometries are valid";
            } else {
                msg = "Invalid geometries: " + numInvalid;
            }
            JOptionPane.showMessageDialog(null, msg, "Geometry results",
                    JOptionPane.INFORMATION_MESSAGE);
        }
    }

    private int validateFeatureGeometry(ProgressListener progress) throws Exception {
        final SimpleFeatureCollection featureCollection = featureSource.getFeatures();

        // Rather than use an iterator, create a FeatureVisitor to check each fature
        class ValidationVisitor implements FeatureVisitor {

            public int numInvalidGeometries = 0;

            public void visit(Feature f) {
                SimpleFeature feature = (SimpleFeature) f;
                Geometry geom = (Geometry) feature.getDefaultGeometry();
                if (geom != null && !geom.isValid()) {
                    numInvalidGeometries++;
                    System.out.println("Invalid Geoemtry: " + feature.getID());
                }
            }
        }

        ValidationVisitor visitor = new ValidationVisitor();

        // Pass visitor and the progress bar to feature collection
        featureCollection.accepts(visitor, progress);
        return visitor.numInvalidGeometries;
    }

    class ExportShapefileAction extends SafeAction {

        ExportShapefileAction() {
            super("Export...");
            putValue(Action.SHORT_DESCRIPTION, "Export using current crs");
        }

        public void action(ActionEvent e) throws Throwable {
            exportToShapefile();
        }
    }

    private void exportToShapefile() throws Exception {
        SimpleFeatureType schema = (SimpleFeatureType) featureSource.getSchema();
        JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
        chooser.setDialogTitle("Save reprojected shapefile");
        chooser.setSaveFile(sourceFile);
        int returnVal = chooser.showSaveDialog(null);
        if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
            return;
        }
        File file = chooser.getSelectedFile();
        if (file.equals(sourceFile)) {
            JOptionPane.showMessageDialog(null, "Cannot replace " + file);
            return;
        }
        CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
        CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
        boolean lenient = true; // allow for some error due to different datums
        MathTransform transform = CRS.findMathTransform(dataCRS, worldCRS, lenient);

        SimpleFeatureCollection featureCollection = featureSource.getFeatures();

        DataStoreFactorySpi factory = new ShapefileDataStoreFactory();
        Map<String, Serializable> create = new HashMap<String, Serializable>();
        create.put("url", file.toURI().toURL());
        create.put("create spatial index", Boolean.TRUE);
        DataStore dataStore = factory.createNewDataStore(create);
        SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(schema, worldCRS);
        dataStore.createSchema(featureType);

        Transaction transaction = new DefaultTransaction("Reproject");
        FeatureWriter<SimpleFeatureType, SimpleFeature> writer =
                dataStore.getFeatureWriterAppend(featureType.getTypeName(), transaction);
        SimpleFeatureIterator iterator = featureCollection.features();
        try {
            while (iterator.hasNext()) {
                // copy the contents of each feature and transform the geometry
                SimpleFeature feature = iterator.next();
                SimpleFeature copy = writer.next();
                copy.setAttributes(feature.getAttributes());

                Geometry geometry = (Geometry) feature.getDefaultGeometry();
                Geometry geometry2 = JTS.transform(geometry, transform);

                copy.setDefaultGeometry(geometry2);
                writer.write();
            }
            transaction.commit();
            JOptionPane.showMessageDialog(null, "Export to shapefile complete");
        } catch (Exception problem) {
            problem.printStackTrace();
            transaction.rollback();
            JOptionPane.showMessageDialog(null, "Export to shapefile failed");
        } finally {
            writer.close();
            iterator.close();
            transaction.close();
        }
    }

    void transoformFeatures() throws Exception {
        SimpleFeatureType schema = featureSource.getSchema();
        CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
        CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
        boolean lenient = true; // allow for some error due to different datums
        MathTransform transform = CRS.findMathTransform(dataCRS, worldCRS, lenient);

        SimpleFeatureCollection featureCollection = featureSource.getFeatures();

        DataStoreFactorySpi factory = new ShapefileDataStoreFactory();

        SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(schema, worldCRS);



    }

    /**
     * This method is called by our feature selection tool when
     * the user has clicked on the map.
     *
     * @param pos map (world) coordinates of the mouse cursor
     */
    void selectFeatures(MapMouseEvent ev) throws FactoryException, TransformException {

        System.out.println("Mouse click at: " + ev.getMapPosition());

        /*
         * Construct a 5x5 pixel rectangle centred on the mouse click position
         */
        Point screenPos = ev.getPoint();

        Rectangle screenRect = new Rectangle(screenPos.x - 2, screenPos.y - 2, 5, 5);

        /*
         * Transform the screen rectangle into bounding box in the coordinate
         * reference system of our map context. Note: we are using a naive method
         * here but GeoTools also offers other, more accurate methods.
         */
        SimpleFeatureType schema = featureSource.getSchema();
        CoordinateReferenceSystem dataCRS = schema.getCoordinateReferenceSystem();
        CoordinateReferenceSystem worldCRS = map.getCoordinateReferenceSystem();
        boolean lenient = true; // allow for some error due to different datums
        MathTransform transform = CRS.findMathTransform(worldCRS, dataCRS, lenient);



        AffineTransform screenToWorld = mapFrame.getMapPane().getScreenToWorldTransform();
        Rectangle2D worldRect = screenToWorld.createTransformedShape(screenRect).getBounds2D();


        ReferencedEnvelope bbox = new ReferencedEnvelope(
                worldRect,
                newCRS);
        // transform from world to target CRS

        CoordinateReferenceSystem targetCRS = schema.getCoordinateReferenceSystem();
        String geometryAttributeName = schema.getGeometryDescriptor().getLocalName();
        bbox = bbox.transform(targetCRS, true, 10);

        /*
         * Create a Filter to select features that intersect with
         * the bounding box
         */
        Filter filter = ff.intersects(ff.property(geometryAttributeName), ff.literal(bbox));

        /*
         * Use the filter to identify the selected features
         */
        try {
            SimpleFeatureCollection selectedFeatures =
                    featureSource.getFeatures(filter);
            selectedFeatures = (SimpleFeatureCollection) mapFrame.getMapContext().getLayer(0).getFeatureSource().getFeatures(filter);

            SimpleFeatureIterator iter = selectedFeatures.features();
            Set<FeatureId> IDs = new HashSet<FeatureId>();
            try {
                while (iter.hasNext()) {
                    SimpleFeature feature = iter.next();
                    IDs.add(feature.getIdentifier());

                    System.out.println("   " + feature.getIdentifier());
                }

            } finally {
                iter.close();
            }

            if (IDs.isEmpty()) {
                System.out.println("   no feature selected");
            }

            displaySelectedFeatures(IDs);

        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }

    private void displaySelectedFeatures(Set<FeatureId> IDs) {
        Iterator<FeatureId> iterator = IDs.iterator();
        while (iterator.hasNext()) {
            FeatureId featureId = iterator.next();
            System.out.println("" + featureId.getID());
        }
        org.geotools.tutorial.MyStyle ms = new MyStyle(geometryType);
        ms.displaySelectedFeatures(IDs, mapFrame);
    }

    /**
     * Retrieve information about the feature geometry
     */
    private void setGeometry() {
        GeometryDescriptor geomDesc = featureSource.getSchema().getGeometryDescriptor();
        geometryAttributeName = geomDesc.getLocalName();

        Class<?> clazz = geomDesc.getType().getBinding();

        if (Polygon.class.isAssignableFrom(clazz)
                || MultiPolygon.class.isAssignableFrom(clazz)) {
            geometryType = GeomType.POLYGON;

        } else if (LineString.class.isAssignableFrom(clazz)
                || MultiLineString.class.isAssignableFrom(clazz)) {

            geometryType = GeomType.LINE;

        } else {
            geometryType = GeomType.POINT;
        }

    }
}
